#!/usr/bin/env python

# Copyright 2012 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""OAuth 2.0 related authorization decorators for handlers."""

import json
import logging
import re
import urllib
from google.appengine.api import oauth
import httplib2
from lib import http, scope_manager
from lib.loggable import loggable
import model

__author__ = 'Maciej Machulak'
__maintainer__ = 'Maciej Machulak'
__email__ = 'mmachulak@google.com'

__copyright__ = 'Copyright 2012, Google Inc.'
__license__ = 'Apache License 2.0'
__version__ = '0.2'
__status__ = 'Prototype'

_TOKEN_INFO_URI = 'https://www.googleapis.com/oauth2/v1/tokeninfo'
_TOKEN_TYPES = ['Bearer', 'OAuth']

class Requester(object):
  """Describes the requester accessing the application."""

  def __init__(self, user, application, scopes):
    self.user = user
    self.application = application
    self.scopes = scopes


def _get_access_token_from_header(authz_header_value,
                                  token_types = _TOKEN_TYPES):
  """Extract the access token value from the Authorization Header value."""

  for single_type in token_types:
    matches_token = re.match(r'(' + single_type + ') (\w.*)',
      authz_header_value)
    if matches_token:
      return single_type, matches_token.group(2)

  return None


def api_authz_required(scopes):
  """Checks if the current call is authorized for any of scopes."""

  def inner_api_authz_required(handler):

    @loggable('checking authorization')
    def check_authz(self, *args):
      logging.debug('required scopes: %s', scopes)
      authz_header = self.request.headers.get(http.Header.authorization)
      if authz_header is None:
        logging.info('api call is not authorized')
        self.response.set_status(401)
        return
      # TODO(mmachulak) logging token? NOT GOOD!
      logging.debug('authz header: %s', authz_header)
      token_type, access_token = _get_access_token_from_header(authz_header)
      logging.debug('access token type: %s', token_type)
      logging.debug('access token: %s', access_token)
      if access_token is None:
        logging.info('token is none')
        self.response.set_status(401)
        return

      requester = _get_requester_for_token(access_token, scopes)

      if requester is None:
        logging.info('token is invalid')
        self.response.set_status(401)
        return
      self.user = requester.user
      self.application = requester.application
      self.scopes = requester.scopes
      handler(self, *args)

    return check_authz

  return inner_api_authz_required


def _get_requester_for_token(access_token, scopes=None, http=None):
  """Validate token and retrieve requester from persistent store."""

  if scopes is None:
    logging.debug('scopes parameter empty')
    scopes = []

  if http is None:
    logging.debug('creating http client')
    http = httplib2.Http()

  token_info_uri = _TOKEN_INFO_URI
  logging.debug('sending request to token info: %s ', token_info_uri)
  query = urllib.urlencode({'access_token': access_token})
  resp, content = http.request(token_info_uri + '?' + query)
  logging.debug(
      'received response from token info endpoint with status: %s',
      resp.status)
  if resp.status == 200:
    logging.debug('successfully retrieved token info')
    email = None
    data = json.loads(content)
    logging.debug(data)
    try:
      user_id = data['user_id']
      scope = data['scope']
      found = False
      authorized_scopes = scope.split(' ')
      authorized_scopes = scope_manager.get_dependent_scopes(authorized_scopes)
      logging.debug('authorized scopes %s', authorized_scopes)
      logging.debug('required scopes %s', scopes)
      for single_scope in scopes:
        logging.debug('checking %s', single_scope)
        if single_scope in authorized_scopes:
          logging.debug('scope valid')
          found = True
          break
      if not found:
        logging.debug('scope not authorized')
        return None

      client_id = data['issued_to']
      application = model.Application.all().filter('client_id = ', client_id)\
                                                 .get()
      if application is None:
        logging.debug(
            'could not find the application with the client_id: %s',
          client_id)
        application = model.Application(client_id=client_id)
        application.put()

      logging.debug('obtaining user by user_id: %s', user_id)
      user = model.User.all().filter('user_id = ', user_id).get()
      if user is None:
        logging.debug(
          'could not find the user for the email address: %s', user_id)
        # Creating the user with this user_id
        user = model.User(user_id=user_id)
        user.put()

      return Requester(user, application, authorized_scopes)

    except KeyError:
      logging.error('there is an error with parsing token info response')
      return None

  elif resp.status == 400:
    d = json.loads(content)
    logging.debug(d)
    logging.error('token is not valid')
    return None
  else:
    logging.debug('could not retrieve token info')
    return None
